-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Perf and fixups #481
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Perf and fixups #481
Conversation
|
||
/** | ||
* This class implements an array-like collection on top of a Firebase location. | ||
*/ | ||
class FirebaseArray implements ChildEventListener { | ||
public interface OnChangedListener { | ||
interface OnChangedListener { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CodingDoug why change the visibility on this interface
as well as the constructor below? I believe @puf just got done approving this particular visibility.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Studio gives me a lint warning saying it's not necessary, which means it's not actually being used outside the package, just like the FirebaseArray class itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's true but Studio is not really considering that this is a library for public consumption, so people may want to subclass etc.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How will anyone subclass OnChangedListener if it's inside a package private class? They won't be able to see it. Are we expecting that people put their classes inside this package so they can make use of it? Also, this interface isn't exposed anywhere in in the public API surface, so there would be no point in extending or implementing it.
If there is a use case here, maybe that needs some more elaboration?
@@ -97,6 +97,7 @@ public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { | |||
mSnapshots.remove(oldIndex); | |||
int newIndex = previousChildKey == null ? 0 : (getIndexForKey(previousChildKey) + 1); | |||
mSnapshots.add(newIndex, snapshot); | |||
mSnapshotMap.put(snapshot.getKey(), newIndex); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't onChildMoved
affect the indexes of every key? I have a feeling that's why @puf didn't use a Map
in the first place but I could be wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup. This won't work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All the tests passed on my end. Do we need more tests?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we have any tests that actually cover item re-order events, we should add one if we are going to change this functionality.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note that the ordering implied by mSnapshots is not changed. All that's really changed here is what getIndexForKey() does to fulfill its job. There can be only one item with a particular key here, and that relationship is simply be expressed in the map now, so it doesn't have to scan the list for the unique key.
There is a test that checks for order changes called testChangePriorities(). It alters the priority of an item so that the array order changes from 1,2,3 to 3,1,2. This test passes.
for (Query ref : refs) { | ||
ref.removeEventListener(mRefs.remove(ref)); | ||
for (Map.Entry<Query, ValueEventListener> entry : mRefs.entrySet()) { | ||
entry.getKey().removeEventListener(entry.getValue()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not equivalent to the previous behavior because there is no call to mRefs.remove()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated
Added some comments but want @puf to be the reviewer here as it's his code. |
I highly recommend doing a performance test before proposing this change. The performance of looking up keys is indeed quadratic, but when we measured it a few months ago that only started becoming significant once we added > 1000 items, which is far beyond what I'd recommend doing. |
@@ -5,6 +5,6 @@ project.ext.submodules = ['database', 'auth', 'storage'] | |||
project.ext.group = 'com.firebaseui' | |||
project.ext.version = '1.0.1' | |||
project.ext.pomdesc = 'Firebase UI Android' | |||
project.ext.buildTools = '25.0.2' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@CodingDoug the version-1.1.0-dev
branch has the latest everything (build tools, support lib, gradle build plugin, etc...). Changing the base branch of this PR to version-1.1.0-dev
would minimize merge conflicts when merging it back into master
! 😄
I tested the reordering thoroughly at some point before we made the first
public release. But that was based on having a script set random priorities
on the items, which I never felt good enough to commit (and have long lost
since).
…On Tue, Jan 3, 2017 at 2:04 PM Sam Stern ***@***.***> wrote:
***@***.**** commented on this pull request.
------------------------------
In database/src/main/java/com/firebase/ui/database/FirebaseArray.java
<#481>:
> @@ -97,6 +97,7 @@ public void onChildMoved(DataSnapshot snapshot, String previousChildKey) {
mSnapshots.remove(oldIndex);
int newIndex = previousChildKey == null ? 0 : (getIndexForKey(previousChildKey) + 1);
mSnapshots.add(newIndex, snapshot);
+ mSnapshotMap.put(snapshot.getKey(), newIndex);
I don't think we have any tests that actually cover item re-order events,
we should add one if we are going to change this functionality.
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#481>, or mute the
thread
<https://github.com/notifications/unsubscribe-auth/AA3w31tWoQzW9DtcK1YagAhiik5QAtyAks5rOsXigaJpZM4LX6fN>
.
|
entry.getKey().removeEventListener(entry.getValue()); | ||
it.remove(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does calling Iterator#remove()
on an Iterator
created from .entrySet()
actually delete from the underlying map? Maybe we should add a test that after cleanup()
we verify mRefs
is empty.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that case is specifically mentioned in the javadoc for Map.entrySet(). https://developer.android.com/reference/java/util/Map.html#entrySet()
@puf The performance test in a simple situation may prove to be "OK", but the fact of the matter is that other work may end up being executed alongside this in the same "batch" of work on the main thread, and if that entire batch exceeds 16ms, Android starts dropping frames. This is a preventative measure to make sure this adapter doesn't work unnecessarily against that 16ms barrier. Alternately, if we don't think FirebaseRecyclerAdapter is a good choice in some known situations, we should probably document those situations so people aren't mislead at the time a choice is being made. |
@samtstern Found that when an item moves front to back (rather than back to front, as was only being tested previously), there is an inconsistency with the recordkeeping of the new map. Fixed with a new test to verify that. |
@puf all my concerns are satisfied now, PTAL to see if yours are as well. |
Before adding code for performance improvements, let's have a look at what the (order of) performance of cases is in the current release and will be after this PR.
Up front: #1 is by far the most common write operation, so its performance should not decrease to benefit #2 and #3. I think #5 is the most common read patter, but that is more based on web knowledge. In fact, I wonder if scrolling up doesn't lead to a 5b where we get the items in reverse. |
I did some experiments with the performance of various write operations. My complete code is here: https://gist.github.com/puf/b2f2940188e11971afc688674d7a1cee The current
|
@@ -74,6 +83,7 @@ public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { | |||
index = getIndexForKey(previousChildKey) + 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this case be rebuilding the "key-to-index" map too?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps! Could you write a test that should succeed, but instead fails?
In the updated version
|
@puf I believe scanArrayForKey is O(n) since it simply iterates a list at most once, looking for the matching key. |
When I tested |
@puf Iteration of an array is definitely not quadratic. Your test harness is making measurements that are inherently quadratic in nature, based on the value of RUN_SIZE. It's performing RUN_SIZE number of lookups against an array of size RUN_SIZE, iterating through RUN_SIZE/2 elements each time. This can only cause quadratic gains as RUN_SIZE increases. If you limit the number of lookups to something constant instead of RUN_SIZE, and you cause some appropriate variance in the values you're looking for, you'll see linear gains at each step up in RUN_SIZE. For example, instead of this:
You could say this to test the worst case scenario to be more fair to the value of NUM_SIZE:
Or, even more accurate to real life, you could use random numbers for the value to look up and assume that a large number of iterations will average things out. |
Ah... I get what you're saying: RUN_SIZE should only change the list size, For performing the append/insert I should loop over count items, but not With that change, the lookups both |
@@ -129,4 +123,17 @@ public Boolean call() throws Exception { | |||
} | |||
}); | |||
} | |||
|
|||
@Test | |||
public void testChangePriorityFrontToBack() throws Exception { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@puf FYI
I am going to start doing QA on version |
Moved TestUtils into correct directory for its package. Removed redundant tests. Reformatted some code and renamed methods for clarity.
…ant time perf during child changes instead of linear time. Removed interfaces internally where exact types would be faster.
0e04c76
to
69dc670
Compare
@samtstern I see you added this to "Needs decision". If we don't want to use |
#579 has been merged so this PR will likely want a rebase. |
Closing this since we haven't discussed this in ~months and it's not in a mergeable state anyway. I believe we've incorporated many of the testing changes. @CodingDoug if you ever convince @puf about all of this we can get back into it, |
Some optimizations - one in particular improves child change events to constant time from linear time (problematic for long lists). Also modernized and fixed up the database tests.
@samtstern @puf